package cmd

import (
	"bufio"
	"log"
	"os"
	"regexp"
	"strings"
	"unicode"

	"github.com/spf13/cobra"
)

// fmtCmd represents the fmt command
var fmtCmd = &cobra.Command{
	Use:   "fmt dir/base.md",
	Short: "md fmt is a tool for formatting a markdown file",
	Long: `md fmt is a tool for formatting a markdown file.
`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) < 1 {
			log.Println("Error: args is not enough")
			forMoreHelp("fmt")
		}
		inputFilePath := args[0]
		fmtFile(inputFilePath)
	},
}

func init() {
	rootCmd.AddCommand(fmtCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// fmtCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// fmtCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func fmtFile(inputFilePath string) {
	if !strings.HasSuffix(inputFilePath, ".md") {
		log.Printf("Error: your input filepath is: %s\n", inputFilePath)
		log.Println("Please input a filepath whose suffix is \".md\"")
		forMoreHelp("fmt")
	}
	// open input file dir/base.md
	iFile, err := os.Open(inputFilePath)
	if err != nil {
		log.Printf("Error when opening input file: %s\n", err)
		forMoreHelp("fmt")
	}
	// create a temp file
	tmpFilePath := inputFilePath + "temp"
	tmpFile, err := os.OpenFile(tmpFilePath, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0644)
	if err != nil {
		log.Printf("Error when opening temp file: %s\n", err)
	}
	tmpWriter := bufio.NewWriter(tmpFile)

	scanner := bufio.NewScanner(iFile)
	for scanner.Scan() {
		//fmt.Println(scanner.Text())
		newStr := punctuation(scanner.Text())
		//fmt.Println(newStr)
		tmpWriter.WriteString(newStr + "\n")
	}
	if err := scanner.Err(); err != nil {
		log.Fatalln(err)
	}
	tmpWriter.Flush()
	// replace iFile with tmpFile
	iFile.Close()
	err = os.Remove(inputFilePath)
	if err != nil {
		log.Fatalln(err)
	}
	tmpFile.Close()
	err = os.Rename(tmpFilePath, inputFilePath)
	if err != nil {
		log.Fatalln(err)
	}
}

func punctuation(src string) (dst string) {
	if isImg(src) || isAnchor(src) {
		return src
	}
	runeSrc := []rune(src)
	size := len(runeSrc)
	addSpace := func(i int) {
		if i == size - 1 {
			return
		}
		nextRune := runeSrc[i+1]
		// The punctuation before the end of quotation mark doesn't need to add space.
		// e.g. "I like you. " No! "I like you." Yes!
		isQuotationMark := nextRune == '"' || nextRune == '”' || nextRune == '’'
		isSpace := unicode.IsSpace(nextRune)
		// ... is rather than . . .
		// two consecutive ...
		isPeriod := nextRune == '.' || nextRune == '…'
		if isQuotationMark || isSpace || isPeriod {
			return
		} else {
			dst += string(' ')
		}
	}
	for i, v := range runeSrc {
		if v == '，' || v == ',' || v == '、' {
			dst += string(',')
			addSpace(i)
		} else if v == '。' || v == '.' {
			dst += string('.')
			addSpace(i)
		} else if v == '（' {
			dst += string('(')
		} else if v == '）' {
			dst += string(')')
		} else if v == '［' {
			dst += string('[')
		} else if v == '］' {
			dst += string(']')
		} else if v == '“' || v == '”' {
			dst += string('"')
		} else if v == '‘' || v == '’' {
			dst += string('\'')
		} else if v == '？' || v == '?' {
			dst += string('?')
			addSpace(i)
		} else if v == '：' || v == ':' {
			dst += string(':')
			addSpace(i)
		} else if v == '；' || v == ';' {
			dst += string(';')
			addSpace(i)
		} else if v == '！'{
			dst += string('!')
			addSpace(i)
		} else if v == '…' {
			dst += "..."
			addSpace(i)
		} else {
			dst += string(v)
		}
	}
	return
}

func forMoreHelp(str string) {
	log.Fatalf("Use \"md %s -h\" for more help\n", str)
}

func isImg(str string) bool {
	reg, _ := regexp.Compile("^!\\[[^]]*]\\(.*\\)")
	res := reg.MatchString(str)
	return res
}

func isAnchor(str string) bool {
	reg, _ := regexp.Compile("\\[[^]]*]\\(.*\\)")
	res := reg.MatchString(str)
	return res
}
